/* eslint no-console: 0 */
/* eslint prefer-reflect : 0*/
/* eslint no-invalid-this: 0*/
import fs from 'fs';
import path from 'path';
import util from 'util';
import LRU from 'lru-cache';
import ejs from 'ejs';
import pretty from 'pretty';
import MemoryFS from 'memory-fs';
import { minify } from 'html-minifier';

function makeMemoryFS(root) {
  const databufs = Buffer.from('0');
  const fsm = new MemoryFS(databufs);

  function iteration(paths, callback) {
    paths = path.normalize(paths);
    const stat = fs.statSync(paths);

    if (stat.isFile()) {
      callback.file(paths);
    } else if (stat.isDirectory()) {
      callback.dirs(paths);
      fs.readdirSync(paths).forEach(v => iteration(paths + '/' + v, callback));
    }
  }

  fsm.mkdirpSync(root);

  iteration(root, {
    file: function (paths) {
      const content = fs.readFileSync(paths);

      fsm.writeFileSync(paths, content);
    },
    dirs: function (paths) {
      if (paths !== root) {
        fsm.mkdirSync(paths);
      }
    }
  });

  return fsm;
}

/**
 * 读写流方式渲染模板
 *
 * @export
 * @param {string} src
 * @returns
 */
function readStream(src) {
  return new Promise((resolve, reject) => {
    const data = [];
    const rstream = fs.createReadStream(src);
    rstream.on('error', () => {
      rstream.close();
      reject(new Error('[Stream Error] Out of the wrong !'));
    });
    rstream.on('end', () => resolve(data.join('')));
    rstream.on('data', (chunk) => data.push(chunk.toString()));
  });
}


/**
 * 模板函数的默认配置
 * @type {Object}
 */
const defaultSettings = {
  cache: true, // --------- 是否缓存
  viewExt: 'html', // ----- 模板后缀
  debug: false, // -------- Debug模式
  locals: {}, // ---------- 传递到模板中的变量、函数
  method: 'file', // ------ 读取模板的方式，可选值 stream file memory
  writeResp: true, // ----- 是否写入到 TCP Handler -> Response中
  layout: 'layout', // ---- 布局模板的名称

  minify: true, // -------- HTML结果压缩
  minifyOption: { // ------ 压缩配置
    collapseWhitespace: true,
    conservativeCollapse: true,
    removeComments: true
  },

  pretty: false, // ------- HTML结果格式化
  prettyOption: {} // ----- HTML美化
};

/**
 * 作为koa2中间件使用
 *
 * @export
 * @param {object} settings
 */
export default function (settings) {
  if (!settings || !settings.root) {
    throw new Error('请指定模板文件根目录');
  }

  settings.minifyOption = Object.assign({}, defaultSettings.minifyOption, settings.minifyOption);
  settings.prettyOption = Object.assign({}, defaultSettings.prettyOption, settings.prettyOption);
  settings = Object.assign({}, defaultSettings, settings);
  settings.root = path.resolve(settings.root);
  settings.viewExt = '.' + (settings.viewExt ? settings.viewExt : 'ejs');

  let fso = {
    readFile: readStream
  };

  if (settings.method === 'file') {

    fso.readFile = util.promisify(fs.readFile);
  } else if (settings.method === 'memory') {

    fso = makeMemoryFS(settings.root);
    ejs.fileLoader = fso.readFileSync;
  }

  ejs.cache = new LRU({
    max: 50
  });

  ejs.localsName = settings.localsName;

  return async function render(view, options) {
    let tplPath = path.join(settings.root, view + settings.viewExt), result;

    options = Object.assign({}, settings.locals, this.state, options);

    const compileOptions = {
      filename: tplPath,
      cache: settings.cache,
      _with: settings._with,
      debug: settings.debug,
      compileDebug: settings.debug && settings.compileDebug,
      delimiter: settings.delimiter,
      root: settings.root
    };

    let tpl = await fso.readFile(tplPath, 'utf8'),
      pager = ejs.compile(tpl, compileOptions);

    result = pager(options);

    const layout = options.layout === false ? false : options.layout || settings.layout;

    if (layout) {
      options.body = result;
      tplPath = path.join(settings.root, layout + settings.viewExt);
      tpl = null;
      pager = null;
      tpl = await fso.readFile(tplPath, 'utf8');
      compileOptions.filename = tplPath + '?' + new Date().getTime();
      pager = ejs.compile(tpl, compileOptions);
      result = pager(options);
    }

    if (settings.pretty) {
      result = pretty(result, settings.prettyOption);
    } else if (settings.minify) {
      result = minify(result, settings.minifyOption);
    }

    tpl = null;
    pager = null;

    const writeResp = options.writeResp === false ? false : options.writeResp || settings.writeResp;

    if (writeResp) {
      this.status = 200;
      this.type = 'html';
      this.body = result;

      return;
    }

    return result;
  };
}
